/*
Copyright 2020-2024 New Vector Ltd.

SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
 */

#import "SecurityViewController.h"

#import "ManageSessionViewController.h"

#import "AvatarGenerator.h"

#import "ThemeService.h"

#import "GeneratedInterface-Swift.h"

@import DesignKit;

// Dev flag to have more options
//#define CROSS_SIGNING_AND_BACKUP_DEV

enum
{
    SECTION_PIN_CODE,
    SECTION_CRYPTO_SESSIONS,
    SECTION_SECURE_BACKUP,
    SECTION_CROSSSIGNING,
    SECTION_CRYPTOGRAPHY,
#ifdef CROSS_SIGNING_AND_BACKUP_DEV
    SECTION_KEYBACKUP,
#endif
    SECTION_ADVANCED,
    SECTION_COUNT
};

enum {
    CROSSSIGNING_INFO,
    CROSSSIGNING_FIRST_ACTION,      // Bootstrap, Reset, Verify this session, Request keys
    CROSSSIGNING_SECOND_ACTION,     // Reset
};

enum {
    PIN_CODE_SETTING,
    PIN_CODE_CHANGE,
    PIN_CODE_BIOMETRICS,
    PIN_CODE_COUNT
};

enum {
    CRYPTOGRAPHY_INFO,
    CRYPTOGRAPHY_EXPORT,    // TODO: To move to SECTION_KEYBACKUP
    CRYPTOGRAPHY_COUNT
};

enum {
    ADVANCED_BLACKLIST_UNVERIFIED_DEVICES,
    ADVANCED_COUNT
};


@interface SecurityViewController () <
SettingsSecureBackupTableViewSectionDelegate,
KeyBackupSetupCoordinatorBridgePresenterDelegate,
#ifdef CROSS_SIGNING_AND_BACKUP_DEV
SettingsKeyBackupTableViewSectionDelegate,
KeyBackupRecoverCoordinatorBridgePresenterDelegate,
#endif
UIDocumentInteractionControllerDelegate,
SecretsRecoveryCoordinatorBridgePresenterDelegate,
SecureBackupSetupCoordinatorBridgePresenterDelegate,
SetPinCoordinatorBridgePresenterDelegate,
TableViewSectionsDelegate>
{
    // Current alert (if any).
    UIAlertController *currentAlert;

    // Devices
    NSMutableArray<MXDevice *> *devicesArray;
    
    // Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change.
    id kThemeServiceDidChangeThemeNotificationObserver;

    // The view used to export e2e keys
    MXKEncryptionKeysExportView *exportView;

    // The document interaction Controller used to export e2e keys
    UIDocumentInteractionController *documentInteractionController;
    NSURL *keyExportsFile;
    NSTimer *keyExportsFileDeletionTimer;
    
    // The current pushed view controller
    UIViewController *pushedViewController;
    
    SettingsSecureBackupTableViewSection *secureBackupSection;
    
#ifdef CROSS_SIGNING_AND_BACKUP_DEV
    SettingsKeyBackupTableViewSection *keyBackupSection;
#endif
    
    KeyBackupSetupCoordinatorBridgePresenter *keyBackupSetupCoordinatorBridgePresenter;
    KeyBackupRecoverCoordinatorBridgePresenter *keyBackupRecoverCoordinatorBridgePresenter;
    SecretsRecoveryCoordinatorBridgePresenter *secretsRecoveryCoordinatorBridgePresenter;
}

@property (nonatomic, strong) TableViewSections *tableViewSections;
@property (nonatomic) BOOL isLoadingDevices;
@property (nonatomic, strong) MXKeyBackupVersion *currentkeyBackupVersion;
@property (nonatomic, strong) SecureBackupSetupCoordinatorBridgePresenter *secureBackupSetupCoordinatorBridgePresenter;
@property (nonatomic, strong) SetPinCoordinatorBridgePresenter *setPinCoordinatorBridgePresenter;
@property (nonatomic, strong) CrossSigningSetupCoordinatorBridgePresenter *crossSigningSetupCoordinatorBridgePresenter;

@property (nonatomic) AnalyticsScreenTracker *screenTracker;

@end

@implementation SecurityViewController

#pragma mark - Setup & Teardown

+ (SecurityViewController*)instantiateWithMatrixSession:(MXSession*)matrixSession
{
    SecurityViewController* viewController = [[UIStoryboard storyboardWithName:@"Security" bundle:[NSBundle mainBundle]] instantiateInitialViewController];
    [viewController addMatrixSession:matrixSession];
    return viewController;
}


#pragma mark - View life cycle

- (void)finalizeInit
{
    [super finalizeInit];
    
    // Setup `MXKViewControllerHandling` properties
    self.enableBarTintColorStatusChange = NO;
    self.rageShakeManager = [RageShakeManager sharedManager];
    
    self.screenTracker = [[AnalyticsScreenTracker alloc] initWithScreen:AnalyticsScreenSettingsSecurity];
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    self.navigationItem.title = [VectorL10n securitySettingsTitle];
    [self vc_setLargeTitleDisplayMode:UINavigationItemLargeTitleDisplayModeNever];
    [self vc_removeBackTitle];

    [self.tableView registerClass:MXKTableViewCellWithLabelAndSwitch.class forCellReuseIdentifier:[MXKTableViewCellWithLabelAndSwitch defaultReuseIdentifier]];
    [self.tableView registerNib:MXKTableViewCellWithTextView.nib forCellReuseIdentifier:[MXKTableViewCellWithTextView defaultReuseIdentifier]];
    [self.tableView registerNib:MXKTableViewCellWithButton.nib forCellReuseIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]];
    [self.tableView registerNib:SectionFooterView.nib forHeaderFooterViewReuseIdentifier:[SectionFooterView defaultReuseIdentifier]];

    // Enable self sizing cells and footers
    self.tableView.rowHeight = UITableViewAutomaticDimension;
    self.tableView.estimatedRowHeight = 50;
    self.tableView.sectionFooterHeight = UITableViewAutomaticDimension;
    self.tableView.estimatedSectionFooterHeight = 50;

    if (self.mainSession.crypto.backup)
    {
        MXDeviceInfo *deviceInfo = [self.mainSession.crypto deviceWithDeviceId:self.mainSession.myDeviceId ofUser:self.mainSession.myUserId];
        
        if (deviceInfo)
        {
            secureBackupSection = [[SettingsSecureBackupTableViewSection alloc] initWithRecoveryService:self.mainSession.crypto.recoveryService keyBackup:self.mainSession.crypto.backup userDevice:deviceInfo];
            secureBackupSection.delegate = self;
            
#ifdef CROSS_SIGNING_AND_BACKUP_DEV
            keyBackupSection = [[SettingsKeyBackupTableViewSection alloc] initWithKeyBackup:self.mainSession.crypto.backup userDevice:deviceInfo];
            keyBackupSection.delegate = self;
#endif
        }
    }
    
    // Observe user interface theme change.
    kThemeServiceDidChangeThemeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kThemeServiceDidChangeThemeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
        
        [self userInterfaceThemeDidChange];
        
    }];
    [self userInterfaceThemeDidChange];
    
    [self registerUserDevicesChangesNotification];
    
    self.tableViewSections = [TableViewSections new];
    self.tableViewSections.delegate = self;
     
    [self updateSections];
}

- (void)userInterfaceThemeDidChange
{
    [ThemeService.shared.theme applyStyleOnNavigationBar:self.navigationController.navigationBar];

    self.activityIndicator.backgroundColor = ThemeService.shared.theme.overlayBackgroundColor;
    
    // Check the table view style to select its bg color.
    self.tableView.backgroundColor = ((self.tableView.style == UITableViewStylePlain) ? ThemeService.shared.theme.backgroundColor : ThemeService.shared.theme.headerBackgroundColor);
    self.view.backgroundColor = self.tableView.backgroundColor;
    self.tableView.separatorColor = ThemeService.shared.theme.lineBreakColor;
    
    [self reloadData];

    [self setNeedsStatusBarAppearanceUpdate];
}

- (UIStatusBarStyle)preferredStatusBarStyle
{
    return ThemeService.shared.theme.statusBarStyle;
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (void)destroy
{
    // Release the potential pushed view controller
    [self releasePushedViewController];
    
    if (documentInteractionController)
    {
        [documentInteractionController dismissPreviewAnimated:NO];
        [documentInteractionController dismissMenuAnimated:NO];
        documentInteractionController = nil;
    }
    
    if (kThemeServiceDidChangeThemeNotificationObserver)
    {
        [[NSNotificationCenter defaultCenter] removeObserver:kThemeServiceDidChangeThemeNotificationObserver];
        kThemeServiceDidChangeThemeNotificationObserver = nil;
    }

    keyBackupSetupCoordinatorBridgePresenter = nil;
    keyBackupRecoverCoordinatorBridgePresenter = nil;
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    [self.screenTracker trackScreen];

    // Release the potential pushed view controller
    [self releasePushedViewController];

    // Refresh display
    [self reloadData];

    // Refresh the current device information in parallel
    [self loadCurrentDeviceInformation];

    // Refresh devices in parallel
    [self loadDevices];
    
    [self loadCrossSigning];
}

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];

    if (currentAlert)
    {
        [currentAlert dismissViewControllerAnimated:NO completion:nil];
        currentAlert = nil;
    }
}

#pragma mark - Internal methods

- (void)updateSections
{
    NSMutableArray<Section*> *sections = [NSMutableArray array];

    BOOL isSecureBackupRequired = self.mainSession.vc_homeserverConfiguration.encryption.isSecureBackupRequired;
    
    // Pin code section
    
    Section *pinCodeSection = [Section sectionWithTag:SECTION_PIN_CODE];
    
    // Header and footer
    if ([PinCodePreferences shared].isBiometricsAvailable)
    {
        pinCodeSection.headerTitle = [VectorL10n pinProtectionSettingsSectionHeaderWithBiometrics:[PinCodePreferences shared].localizedBiometricsName];
    } else {
        pinCodeSection.headerTitle = [VectorL10n pinProtectionSettingsSectionHeader];
    }
    if (PinCodePreferences.shared.isPinSet)
    {
        pinCodeSection.footerTitle = VectorL10n.pinProtectionSettingsSectionFooter;
    }
    
    // Rows
    [pinCodeSection addRowWithTag:PIN_CODE_SETTING];
    
    if ([PinCodePreferences shared].isPinSet)
    {
        [pinCodeSection addRowWithTag:PIN_CODE_CHANGE];
    }

    if ([PinCodePreferences shared].isBiometricsAvailable)
    {
        [pinCodeSection addRowWithTag:PIN_CODE_BIOMETRICS];
    }
    
    [sections addObject:pinCodeSection];
    
    // Crypto sessions section
        
    if (RiotSettings.shared.settingsSecurityScreenShowSessions && !RiotSettings.shared.enableNewSessionManager)
    {
        Section *sessionsSection = [Section sectionWithTag:SECTION_CRYPTO_SESSIONS];
        
        sessionsSection.headerTitle = [VectorL10n securitySettingsCryptoSessions];
        
        if (self.showLoadingDevicesInformation)
        {
            sessionsSection.footerTitle = VectorL10n.securitySettingsCryptoSessionsLoading;
        }
        else
        {
            sessionsSection.footerTitle = VectorL10n.securitySettingsCryptoSessionsDescription2;
            [sessionsSection addRowsWithCount:devicesArray.count];
        }
        
        [sections addObject:sessionsSection];
    }
    
    // Secure backup

    if (!isSecureBackupRequired)
    {
        Section *secureBackupSection = [Section sectionWithTag:SECTION_SECURE_BACKUP];
        secureBackupSection.headerTitle = [VectorL10n securitySettingsSecureBackup];
        secureBackupSection.footerTitle = VectorL10n.securitySettingsSecureBackupDescription;

        [secureBackupSection addRowsWithCount:self->secureBackupSection.numberOfRows];

        [sections addObject:secureBackupSection];
    }
    
    // Cross-Signing
    
    Section *crossSigningSection = [Section sectionWithTag:SECTION_CROSSSIGNING];
    crossSigningSection.headerTitle = [VectorL10n securitySettingsCrosssigning];
    
    [crossSigningSection addRowsWithCount:[self numberOfRowsInCrossSigningSection]];
    
    [sections addObject:crossSigningSection];
    
    // Cryptography
    
    Section *cryptographySection = [Section sectionWithTag:SECTION_CRYPTOGRAPHY];
    cryptographySection.headerTitle = [VectorL10n securitySettingsCryptography];
    
    if (RiotSettings.shared.settingsSecurityScreenShowCryptographyInfo)
    {
        [cryptographySection addRowWithTag:CRYPTOGRAPHY_INFO];
    }
    
    if (RiotSettings.shared.settingsSecurityScreenShowCryptographyExport && !isSecureBackupRequired)
    {
        [cryptographySection addRowWithTag:CRYPTOGRAPHY_EXPORT];
    }

    if (cryptographySection.rows.count)
    {
        [sections addObject:cryptographySection];
    }

#ifdef CROSS_SIGNING_AND_BACKUP_DEV
    
    // Keybackup
    
    Section *keybackupSection = [Section sectionWithTag:SECTION_KEYBACKUP];
    keybackupSection.headerTitle = [VectorL10n securitySettingsBackup];
    
    [keybackupSection addRowsWithCount:self->keyBackupSection.numberOfRows];
    
    [sections addObject:keybackupSection];
        
#endif
    
    // Advanced
    
    if (RiotSettings.shared.settingsSecurityScreenShowAdvancedUnverifiedDevices)
    {
        Section *advancedSection = [Section sectionWithTag:SECTION_ADVANCED];
        advancedSection.headerTitle = VectorL10n.securitySettingsAdvanced;
        advancedSection.footerTitle = VectorL10n.securitySettingsBlacklistUnverifiedDevicesDescription;
        
        [advancedSection addRowWithTag:ADVANCED_BLACKLIST_UNVERIFIED_DEVICES];
        [sections addObject:advancedSection];
    }

    // Update sections
    
    self.tableViewSections.sections = sections;
}

- (BOOL)showLoadingDevicesInformation
{
    return self.isLoadingDevices && devicesArray.count == 0;
}

- (void)pushViewController:(UIViewController*)viewController
{
    // Keep ref on pushed view controller
    pushedViewController = viewController;
    [self.navigationController pushViewController:viewController animated:YES];
}

- (void)releasePushedViewController
{
    if (pushedViewController)
    {
        if ([pushedViewController isKindOfClass:[UINavigationController class]])
        {
            UINavigationController *navigationController = (UINavigationController*)pushedViewController;
            for (id subViewController in navigationController.viewControllers)
            {
                if ([subViewController respondsToSelector:@selector(destroy)])
                {
                    [subViewController destroy];
                }
            }
        }
        else if ([pushedViewController respondsToSelector:@selector(destroy)])
        {
            [(id)pushedViewController destroy];
        }

        pushedViewController = nil;
    }
}

- (void)reset
{
    // Remove observers
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (void)loadCurrentDeviceInformation
{
    // Refresh the current device information
    MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject;
    [account loadDeviceInformation:^{

        // Refresh all the table (A slide down animation is observed when we limit the refresh to the concerned section).
        // Note: The use of 'reloadData' handles the case where the account has been logged out.
        [self reloadData];

    } failure:nil];
}

- (NSAttributedString*)cryptographyInformation
{
    // TODO Handle multi accounts
    MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject;

    // Crypto information
    NSMutableAttributedString *cryptoInformationString = [[NSMutableAttributedString alloc]
                                                          initWithString:[VectorL10n settingsCryptoDeviceName]
                                                          attributes:@{NSForegroundColorAttributeName : ThemeService.shared.theme.textPrimaryColor,
                                                                       NSFontAttributeName: [UIFont systemFontOfSize:17]}];
    [cryptoInformationString appendAttributedString:[[NSMutableAttributedString alloc]
                                                     initWithString:account.device.displayName ? account.device.displayName : @""
                                                     attributes:@{NSForegroundColorAttributeName : ThemeService.shared.theme.textPrimaryColor,
                                                                  NSFontAttributeName: [UIFont systemFontOfSize:17]}]];

    [cryptoInformationString appendAttributedString:[[NSMutableAttributedString alloc]
                                                     initWithString:[VectorL10n settingsCryptoDeviceId]
                                                     attributes:@{NSForegroundColorAttributeName : ThemeService.shared.theme.textPrimaryColor,
                                                                  NSFontAttributeName: [UIFont systemFontOfSize:17]}]];
    [cryptoInformationString appendAttributedString:[[NSMutableAttributedString alloc]
                                                     initWithString:account.device.deviceId ? account.device.deviceId : @""
                                                     attributes:@{NSForegroundColorAttributeName : ThemeService.shared.theme.textPrimaryColor,
                                                                  NSFontAttributeName: [UIFont systemFontOfSize:17]}]];

    [cryptoInformationString appendAttributedString:[[NSMutableAttributedString alloc]
                                                     initWithString:[VectorL10n settingsCryptoDeviceKey]
                                                     attributes:@{NSForegroundColorAttributeName : ThemeService.shared.theme.textPrimaryColor,
                                                                  NSFontAttributeName: [UIFont systemFontOfSize:17]}]];
    NSString *fingerprint = account.mxSession.crypto.deviceEd25519Key;
    if (fingerprint)
    {
        fingerprint = [MXTools addWhiteSpacesToString:fingerprint every:4];
    }
    [cryptoInformationString appendAttributedString:[[NSMutableAttributedString alloc]
                                                     initWithString:fingerprint ? fingerprint : @""
                                                     attributes:@{NSForegroundColorAttributeName : ThemeService.shared.theme.textPrimaryColor,
                                                                  NSFontAttributeName: [UIFont boldSystemFontOfSize:17]}]];

    return cryptoInformationString;
}

- (void)loadDevices
{
    self.isLoadingDevices = YES;
    
    // Refresh the account devices list
    MXWeakify(self);
    [self.mainSession.matrixRestClient devices:^(NSArray<MXDevice *> *devices) {
        MXStrongifyAndReturnIfNil(self);
        
        self.isLoadingDevices = NO;

        if (devices)
        {
            self->devicesArray = [NSMutableArray arrayWithArray:devices];

            // Sort devices according to the last seen date.
            NSComparator comparator = ^NSComparisonResult(MXDevice *deviceA, MXDevice *deviceB) {

                if (deviceA.lastSeenTs > deviceB.lastSeenTs)
                {
                    return NSOrderedAscending;
                }
                if (deviceA.lastSeenTs < deviceB.lastSeenTs)
                {
                    return NSOrderedDescending;
                }

                return NSOrderedSame;
            };

            // Sort devices list
            [self->devicesArray sortUsingComparator:comparator];
        }
        else
        {
            self->devicesArray = nil;

        }

        // Refresh all the table (A slide down animation is observed when we limit the refresh to the concerned section).
        // Note: The use of 'reloadData' handles the case where the account has been logged out.
        [self reloadData];

    } failure:^(NSError *error) {

        self.isLoadingDevices = NO;
        
        // Display the data that has been loaded last time
        // Note: The use of 'reloadData' handles the case where the account has been logged out.
        [self reloadData];

    }];
}

- (void)reloadData
{
    // Update table view sections and trigger a tableView reloadData
    [self updateSections];
}


#pragma mark - Data update

- (void)registerUserDevicesChangesNotification
{
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(onDeviceInfoTrustLevelDidChangeNotification:)
                                                 name:MXDeviceInfoTrustLevelDidChangeNotification
                                               object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(crossSigningInfoTrustLevelDidChangeNotification:)
                                                 name:MXCrossSigningInfoTrustLevelDidChangeNotification
                                               object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(onDidUpdateUsersDevicesNotification:)
                                                 name:MXDeviceListDidUpdateUsersDevicesNotification
                                               object:nil];
}

- (void)onDidUpdateUsersDevicesNotification:(NSNotification*)notification
{
    NSDictionary *usersDevices = notification.userInfo;
    
    if ([usersDevices.allKeys containsObject:self.mainSession.myUserId])
    {
        [self loadDevices];
    }
}

- (void)onDeviceInfoTrustLevelDidChangeNotification:(NSNotification*)notification
{
    MXDeviceInfo *deviceInfo = notification.object;
    
    NSString *userId = deviceInfo.userId;
    if ([userId isEqualToString:self.mainSession.myUserId])
    {
        [self loadDevices];
    }
}

- (void)crossSigningInfoTrustLevelDidChangeNotification:(NSNotification*)notification
{
    MXCrossSigningInfo *crossSigningInfo = notification.object;
    
    NSString *userId = crossSigningInfo.userId;
    if ([userId isEqualToString:self.mainSession.myUserId])
    {
        [self loadDevices];
    }
}


#pragma mark - Cross-signing

- (void)loadCrossSigning
{
    id<MXCrossSigning> crossSigning = self.mainSession.crypto.crossSigning;
    
    [crossSigning refreshStateWithSuccess:^(BOOL stateUpdated) {
        if (stateUpdated)
        {
            [self reloadData];
        }
    } failure:^(NSError * _Nonnull error) {
        MXLogDebug(@"[SecurityVC] loadCrossSigning: Cannot refresh cross-signing state. Error: %@", error);
    }];
}

- (NSInteger)numberOfRowsInCrossSigningSection
{
    NSInteger numberOfRowsInCrossSigningSection;
    
    id<MXCrossSigning> crossSigning = self.mainSession.crypto.crossSigning;
    switch (crossSigning.state)
    {
        case MXCrossSigningStateNotBootstrapped:                // Action: Bootstrap
        case MXCrossSigningStateCanCrossSign:                   // Action: Reset
            numberOfRowsInCrossSigningSection = CROSSSIGNING_FIRST_ACTION + 1;
            break;
        case MXCrossSigningStateCrossSigningExists:             // Actions: Verify this session, Reset
        case MXCrossSigningStateTrustCrossSigning:              // Actions: Request keys, Reset
            numberOfRowsInCrossSigningSection = CROSSSIGNING_SECOND_ACTION + 1;
            break;
    }
    
    return numberOfRowsInCrossSigningSection;
}

- (NSAttributedString*)crossSigningInformation
{
    id<MXCrossSigning> crossSigning = self.mainSession.crypto.crossSigning;
    
    NSString *crossSigningInformation;
    switch (crossSigning.state)
    {
        case MXCrossSigningStateNotBootstrapped:
            crossSigningInformation = [VectorL10n securitySettingsCrosssigningInfoNotBootstrapped];
            break;
        case MXCrossSigningStateCrossSigningExists:
            crossSigningInformation = [VectorL10n securitySettingsCrosssigningInfoExists];
            break;
        case MXCrossSigningStateTrustCrossSigning:
            crossSigningInformation = [VectorL10n securitySettingsCrosssigningInfoTrusted];
            break;
        case MXCrossSigningStateCanCrossSign:
            crossSigningInformation = [VectorL10n securitySettingsCrosssigningInfoOk];
            
            if (![self.mainSession.crypto.recoveryService hasSecretLocally:MXSecretId.crossSigningMaster])
            {
                crossSigningInformation = [crossSigningInformation stringByAppendingString:@"\n\n⚠️ The MSK is missing. Verify this device again or use the Secure Backup below to synchronise your keys accross your devices"];
            }
            break;
    }
    
    return [[NSAttributedString alloc] initWithString:crossSigningInformation
                                           attributes:@{
                                                        NSForegroundColorAttributeName : ThemeService.shared.theme.textPrimaryColor,
                                                        NSFontAttributeName: [UIFont systemFontOfSize:17]
                                                        }];
}

- (UITableViewCell*)crossSigningButtonCellInTableView:(UITableView*)tableView forAction:(NSInteger)action
{
    // Get a button cell
    MXKTableViewCellWithButton *buttonCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]];
    if (!buttonCell)
    {
        buttonCell = [[MXKTableViewCellWithButton alloc] init];
    }

    [buttonCell.mxkButton setTintColor:ThemeService.shared.theme.tintColor];
    buttonCell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17];
    
    [buttonCell.mxkButton removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside];
    buttonCell.mxkButton.accessibilityIdentifier = nil;
    
    // And customise it
    id<MXCrossSigning> crossSigning = self.mainSession.crypto.crossSigning;
    switch (crossSigning.state)
    {
        case MXCrossSigningStateNotBootstrapped:                // Action: Bootstrap
            [self setUpcrossSigningButtonCellForBootstrap:buttonCell];
            break;
        case MXCrossSigningStateCanCrossSign:                   // Action: Reset
            [self setUpcrossSigningButtonCellForReset:buttonCell];
            break;
        case MXCrossSigningStateCrossSigningExists:             // Actions: Verify this session, Reset
            switch (action)
            {
                case CROSSSIGNING_FIRST_ACTION:
                    [self setUpcrossSigningButtonCellForCompletingSecurity:buttonCell];
                    break;
                case CROSSSIGNING_SECOND_ACTION:
                    [self setUpcrossSigningButtonCellForReset:buttonCell];
                    break;
            }
            break;
        case MXCrossSigningStateTrustCrossSigning:              // Actions: Request keys, Reset
            switch (action)
            {
                case CROSSSIGNING_FIRST_ACTION:
                    // By verifying our device again, it will get cross-signing keys by gossiping
                    [self setUpcrossSigningButtonCellForCompletingSecurity:buttonCell];
                    break;
                case CROSSSIGNING_SECOND_ACTION:
                    [self setUpcrossSigningButtonCellForReset:buttonCell];
                    break;
            }
            break;
    }
    
    return buttonCell;
}

- (void)setUpcrossSigningButtonCellForBootstrap:(MXKTableViewCellWithButton*)buttonCell
{
    NSString *btnTitle = [VectorL10n securitySettingsCrosssigningBootstrap];
    [buttonCell.mxkButton setTitle:btnTitle forState:UIControlStateNormal];
    [buttonCell.mxkButton setTitle:btnTitle forState:UIControlStateHighlighted];
 
    [buttonCell.mxkButton addTarget:self action:@selector(setupCrossSigning:) forControlEvents:UIControlEventTouchUpInside];
}

- (void)setupCrossSigning:(id)sender
{
    [self setupCrossSigningWithTitle:@"Set up cross-signing"    // TODO
                             message:[VectorL10n securitySettingsUserPasswordDescription]
                             success:^{
                             } failure:^(NSError *error) {
                             }];
}

- (void)setupCrossSigningWithTitle:(NSString*)title
                           message:(NSString*)message
                           success:(void (^)(void))success
                           failure:(void (^)(NSError *error))failure

{
    [self startActivityIndicator];
    
    MXWeakify(self);
    
    void (^animationCompletion)(void) = ^void () {
        MXStrongifyAndReturnIfNil(self);
        
        [self stopActivityIndicator];
        [self.crossSigningSetupCoordinatorBridgePresenter dismissWithAnimated:YES completion:^{}];
        self.crossSigningSetupCoordinatorBridgePresenter = nil;
    };
    
    CrossSigningSetupCoordinatorBridgePresenter *crossSigningSetupCoordinatorBridgePresenter = [[CrossSigningSetupCoordinatorBridgePresenter alloc] initWithSession:self.mainSession];
        
    [crossSigningSetupCoordinatorBridgePresenter presentWith:title
                                                     message:message
                                                        from:self
                                                    animated:YES
                                                     success:^{
        animationCompletion();
        [self reloadData];
        success();
    } cancel:^{
        animationCompletion();
        failure(nil);
    } failure:^(NSError * _Nonnull error) {
        animationCompletion();
        [self reloadData];
        [[AppDelegate theDelegate] showErrorAsAlert:error];
        failure(error);
    }];
    
    self.crossSigningSetupCoordinatorBridgePresenter = crossSigningSetupCoordinatorBridgePresenter;
}

- (void)resetCrossSigning:(id)sender
{
    [currentAlert dismissViewControllerAnimated:NO completion:nil];
    
    // Double confirmation
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Are you sure?"  // TODO
                                                                             message:@"Anyone you have verified with will see security alerts. You almost certainly don't want to do this, unless you've lost every device you can cross-sign from."     // TODO
                                                                      preferredStyle:UIAlertControllerStyleAlert];
    
    [alertController addAction:[UIAlertAction actionWithTitle:@"Reset"
                                                        style:UIAlertActionStyleDefault
                                                      handler:^(UIAlertAction * action)
                                {
                                    // Setup and reset are the same thing
                                    [self setupCrossSigning:nil];
                                }]];
    
    [alertController addAction:[UIAlertAction actionWithTitle:[VectorL10n cancel]
                                                                style:UIAlertActionStyleCancel
                                                              handler:nil]];
    
    [self presentViewController:alertController animated:YES completion:nil];
    currentAlert = alertController;
}

- (void)setUpcrossSigningButtonCellForReset:(MXKTableViewCellWithButton*)buttonCell
{
    NSString *btnTitle = [VectorL10n securitySettingsCrosssigningReset];
    [buttonCell.mxkButton setTitle:btnTitle forState:UIControlStateNormal];
    [buttonCell.mxkButton setTitle:btnTitle forState:UIControlStateHighlighted];
    
    buttonCell.mxkButton.tintColor = ThemeService.shared.theme.warningColor;
    
    [buttonCell.mxkButton addTarget:self action:@selector(resetCrossSigning:) forControlEvents:UIControlEventTouchUpInside];
}

- (void)setUpcrossSigningButtonCellForCompletingSecurity:(MXKTableViewCellWithButton*)buttonCell
{
    NSString *btnTitle = [VectorL10n securitySettingsCrosssigningCompleteSecurity];
    [buttonCell.mxkButton setTitle:btnTitle forState:UIControlStateNormal];
    [buttonCell.mxkButton setTitle:btnTitle forState:UIControlStateHighlighted];
    
    [buttonCell.mxkButton addTarget:self action:@selector(presentCompleteSecurity) forControlEvents:UIControlEventTouchUpInside];
}

- (void)displayComingSoon
{
    [[AppDelegate theDelegate] showAlertWithTitle:nil message:[VectorL10n securitySettingsComingSoon:AppInfo.current.displayName :AppInfo.current.displayName]];
}


#pragma mark - SSSS

- (NSString*)secureBackupInformation
{
    NSString *secureBackupInformation;
    
    MXRecoveryService *recoveryService =  self.mainSession.crypto.recoveryService;
    
    if (recoveryService.hasRecovery)
    {
        NSMutableString *mutableString = [@"Your account has a Secure Backup.\n" mutableCopy];
        
        // Check all keys that should be in the SSSSS
        // TODO: Check obsoletes ones but need spec update
        
        BOOL hasWarning = NO;
        NSString *keyState = [self informationForSecret:MXSecretId.crossSigningMaster secretName:@"Cross-signing" hasWarning:&hasWarning];
        if (keyState)
        {
            [mutableString appendString:keyState];
        }
        
        keyState = [self informationForSecret:MXSecretId.crossSigningSelfSigning secretName:@"Self signing" hasWarning:&hasWarning];
        if (keyState)
        {
            [mutableString appendString:keyState];
        }

        keyState = [self informationForSecret:MXSecretId.crossSigningUserSigning secretName:@"User signing" hasWarning:&hasWarning];
        if (keyState)
        {
            [mutableString appendString:keyState];
        }
        
        keyState = [self informationForSecret:MXSecretId.keyBackup secretName:@"Message Backup" hasWarning:&hasWarning];
        if (keyState)
        {
            [mutableString appendString:keyState];
        }
        else
        {
            if (self.mainSession.crypto.backup.keyBackupVersion)
            {
                [mutableString appendString:@"\n\n⚠️ The key of your current Message backup is not in the Secure Backup. Restore it first (see below)."];
            }
            else
            {
                [mutableString appendString:@"\n\n⚠️ Consider create a Message Backup (see below)."];
            }
        }
        
        if (!hasWarning)
        {
            [mutableString appendFormat:@"\n\nIf you are facing an issue, synchronise your Secure Backup."];
        }
        
        secureBackupInformation = mutableString;
    }
    else
    {
        if (self.canSetupSecureBackup)
        {
            secureBackupInformation = [NSString stringWithFormat:@"No Secure Backup. Create one.\n-----\nKeys to back up: %@", recoveryService.secretsStoredLocally];
        }
        else
        {
            secureBackupInformation = [NSString stringWithFormat:@"No Secure Backup. Set up cross-signing first (see above)"];
        }
    }

    return secureBackupInformation;
}

- (nullable NSString*)informationForSecret:(NSString*)secretId secretName:(NSString*)secretName hasWarning:(BOOL*)hasWarning
{
    NSString *information;
    
    MXRecoveryService *recoveryService = self.mainSession.crypto.recoveryService;
    
    if ([recoveryService hasSecretWithSecretId:secretId])
    {
        if ([recoveryService hasSecretLocally:secretId])
        {
            information = [NSString stringWithFormat:@"\n ✅ %@ is in the backup", secretName];
        }
        else
        {
            information = [NSString stringWithFormat:@"\n ⚠️ %@ is in the backup but not locally. Tap Synchronise", secretName];
            *hasWarning |= YES;
        }
    }
    else
    {
        if ([recoveryService hasSecretLocally:secretId])
        {
            information = [NSString stringWithFormat:@"\n ⚠️ %@ is not in the backup. Tap Synchronise", secretName];
            *hasWarning |= YES;
        }
    }
    
    return information;
}

- (BOOL)canSetupSecureBackup
{
    // Accept to create a setup only if we have the 3 cross-signing keys
    // This is the path to have a sane state
    MXRecoveryService *recoveryService = self.mainSession.crypto.recoveryService;
    
    NSArray *crossSigningServiceSecrets = @[
                                            MXSecretId.crossSigningMaster,
                                            MXSecretId.crossSigningSelfSigning,
                                            MXSecretId.crossSigningUserSigning];
    
    return ([recoveryService.secretsStoredLocally mx_intersectArray:crossSigningServiceSecrets].count
            == crossSigningServiceSecrets.count);
}

- (void)setupSecureBackup
{
    if (self.canSetupSecureBackup)
    {
        [self setupSecureBackup2];
    }
    else
    {
        // Set up cross-signing first
        [self setupCrossSigningWithTitle:[VectorL10n secureKeyBackupSetupIntroTitle]
                                 message:[VectorL10n securitySettingsUserPasswordDescription]
                                 success:^{
                                     [self setupSecureBackup2];
                                 } failure:^(NSError *error) {
                                 }];
    }
}

- (void)setupSecureBackup2
{
    SecureBackupSetupCoordinatorBridgePresenter *secureBackupSetupCoordinatorBridgePresenter = [[SecureBackupSetupCoordinatorBridgePresenter alloc] initWithSession:self.mainSession allowOverwrite:YES];
    secureBackupSetupCoordinatorBridgePresenter.delegate = self;
    
    [secureBackupSetupCoordinatorBridgePresenter presentFrom:self animated:YES];
    
    self.secureBackupSetupCoordinatorBridgePresenter = secureBackupSetupCoordinatorBridgePresenter;
}


#pragma mark - Segues

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    // Keep ref on destinationViewController
    [super prepareForSegue:segue sender:sender];

    // FIXME add night mode
}

#pragma mark - UITableView data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return self.tableViewSections.sections.count;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    Section *tableSection = [self.tableViewSections sectionAtIndex:section];
    return tableSection.rows.count;
}

- (MXKTableViewCellWithLabelAndSwitch*)getLabelAndSwitchCell:(UITableView*)tableView forIndexPath:(NSIndexPath *)indexPath
{
    MXKTableViewCellWithLabelAndSwitch *cell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithLabelAndSwitch defaultReuseIdentifier] forIndexPath:indexPath];

    cell.mxkLabelLeadingConstraint.constant = tableView.vc_separatorInset.left;
    cell.mxkSwitchTrailingConstraint.constant = 15;

    cell.mxkLabel.textColor = ThemeService.shared.theme.textPrimaryColor;

    [cell.mxkSwitch removeTarget:self action:nil forControlEvents:UIControlEventValueChanged];

    // Force layout before reusing a cell (fix switch displayed outside the screen)
    [cell layoutIfNeeded];

    return cell;
}

- (MXKTableViewCell*)getDefaultTableViewCell:(UITableView*)tableView
{
    MXKTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCell defaultReuseIdentifier]];
    if (!cell)
    {
        cell = [[MXKTableViewCell alloc] init];
    }
    else
    {
        cell.selectionStyle = UITableViewCellSelectionStyleDefault;
        cell.accessoryType = UITableViewCellAccessoryNone;
        cell.accessoryView = nil;
        cell.imageView.image = nil;
    }
    cell.textLabel.accessibilityIdentifier = nil;
    cell.textLabel.font = [UIFont systemFontOfSize:17];
    cell.textLabel.textColor = ThemeService.shared.theme.textPrimaryColor;
    cell.contentView.backgroundColor = UIColor.clearColor;

    return cell;
}

- (MXKTableViewCell*)deviceCellWithDevice:(MXDevice*)device forTableView:(UITableView*)tableView
{
    MXKTableViewCell *cell = [self getDefaultTableViewCell:tableView];
    NSString *name = device.displayName;
    NSString *deviceId = device.deviceId;
    cell.textLabel.text = (name.length ? [NSString stringWithFormat:@"%@ (%@)", name, deviceId] : [NSString stringWithFormat:@"(%@)", deviceId]);
    cell.textLabel.numberOfLines = 0;
    [cell vc_setAccessoryDisclosureIndicatorWithCurrentTheme];

    if ([deviceId isEqualToString:self.mainSession.matrixRestClient.credentials.deviceId])
    {
        cell.textLabel.font = [UIFont boldSystemFontOfSize:17];
    }

    cell.imageView.image = [self shieldImageForDevice:deviceId];

    return cell;
}

- (UIImage*)shieldImageForDevice:(NSString*)deviceId
{
    if (!self.mainSession.crypto.crossSigning.canCrossSign)
    {
        if ([deviceId isEqualToString:self.mainSession.myDeviceId])
        {
            return AssetImages.encryptionWarning.image;
        }
        else
        {
            return AssetImages.encryptionNormal.image;
        }
    }
    
    UIImage* shieldImageForDevice = AssetImages.encryptionWarning.image;
    MXDeviceInfo *device = [self.mainSession.crypto deviceWithDeviceId:deviceId ofUser:self.mainSession.myUser.userId];
    if (device.trustLevel.isVerified)
    {
        shieldImageForDevice = AssetImages.encryptionTrusted.image;
    }

    return shieldImageForDevice;
}

- (MXKTableViewCellWithTextView*)textViewCellForTableView:(UITableView*)tableView atIndexPath:(NSIndexPath *)indexPath
{
    MXKTableViewCellWithTextView *textViewCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithTextView defaultReuseIdentifier] forIndexPath:indexPath];

    textViewCell.mxkTextView.textColor = ThemeService.shared.theme.textPrimaryColor;
    textViewCell.mxkTextView.font = [UIFont systemFontOfSize:17];
    textViewCell.mxkTextView.backgroundColor = [UIColor clearColor];
    textViewCell.mxkTextViewLeadingConstraint.constant = tableView.vc_separatorInset.left;
    textViewCell.mxkTextViewTrailingConstraint.constant = tableView.vc_separatorInset.right;
    textViewCell.mxkTextView.accessibilityIdentifier = nil;

    return textViewCell;
}

- (MXKTableViewCellWithButton *)buttonCellForTableView:(UITableView*)tableView atIndexPath:(NSIndexPath *)indexPath
{
    MXKTableViewCellWithButton *cell = [self.tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier] forIndexPath:indexPath];
    
    if (!cell)
    {
        cell = [[MXKTableViewCellWithButton alloc] init];
    }
    else
    {
        // Fix https://github.com/vector-im/riot-ios/issues/1354
        cell.mxkButton.titleLabel.text = nil;
        cell.mxkButton.enabled = YES;
    }
    
    cell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17];
    [cell.mxkButton setTintColor:ThemeService.shared.theme.tintColor];
    
    return cell;
}

- (MXKTableViewCellWithButton *)buttonCellWithTitle:(NSString*)title
                                           action:(SEL)action
                                       forTableView:(UITableView*)tableView
                                        atIndexPath:(NSIndexPath *)indexPath
{
    MXKTableViewCellWithButton *cell = [self buttonCellForTableView:tableView atIndexPath:indexPath];
    
    
    [cell.mxkButton setTitle:title forState:UIControlStateNormal];
    [cell.mxkButton setTitle:title forState:UIControlStateHighlighted];
    
    [cell.mxkButton removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside];
    [cell.mxkButton addTarget:self action:action forControlEvents:UIControlEventTouchUpInside];
    cell.mxkButton.accessibilityIdentifier = nil;
    
    return cell;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSIndexPath *tagsIndexPath = [self.tableViewSections tagsIndexPathFromTableViewIndexPath:indexPath];
    NSInteger sectionTag = tagsIndexPath.section;
    NSInteger rowTag = tagsIndexPath.row;

    // set the cell to a default value to avoid application crashes
    UITableViewCell *cell = [[UITableViewCell alloc] init];
    cell.backgroundColor = [UIColor redColor];

    MXSession* session = self.mainSession;
    if (sectionTag == SECTION_PIN_CODE)
    {
        if (rowTag == PIN_CODE_SETTING)
        {
            if ([PinCodePreferences shared].forcePinProtection)
            {
                cell = [self getDefaultTableViewCell:tableView];
                cell.textLabel.text = [VectorL10n pinProtectionSettingsEnabledForced];
            }
            else
            {
                MXKTableViewCellWithLabelAndSwitch *switchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath];
                
                switchCell.mxkLabel.text = [VectorL10n pinProtectionSettingsEnablePin];
                switchCell.mxkSwitch.on = [PinCodePreferences shared].isPinSet;
                [switchCell.mxkSwitch addTarget:self action:@selector(enablePinCodeSwitchValueChanged:) forControlEvents:UIControlEventValueChanged];
                
                cell = switchCell;
            }
            
            cell.selectionStyle = UITableViewCellSelectionStyleNone;
        }
        else if (rowTag == PIN_CODE_CHANGE)
        {
            cell = [self buttonCellWithTitle:[VectorL10n pinProtectionSettingsChangePin] action:@selector(changePinCode:) forTableView:tableView atIndexPath:indexPath];
        }
        else if (rowTag == PIN_CODE_BIOMETRICS)
        {
            MXKTableViewCellWithLabelAndSwitch *switchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath];
            
            switchCell.mxkLabel.text = [VectorL10n biometricsSettingsEnableX:[PinCodePreferences shared].localizedBiometricsName];
            switchCell.mxkSwitch.on = [PinCodePreferences shared].isBiometricsSet;
            switchCell.mxkSwitch.enabled = [PinCodePreferences shared].isBiometricsAvailable;
            [switchCell.mxkSwitch addTarget:self action:@selector(enableBiometricsSwitchValueChanged:) forControlEvents:UIControlEventValueChanged];
            
            cell = switchCell;
        }
    }
    else if (sectionTag == SECTION_CRYPTO_SESSIONS)
    {
        cell = [self deviceCellWithDevice:devicesArray[rowTag] forTableView:tableView];
    }
    else if (sectionTag == SECTION_SECURE_BACKUP)
    {
        cell = [secureBackupSection cellForRowAtRow:rowTag];
    }
#ifdef CROSS_SIGNING_AND_BACKUP_DEV
    else if (sectionTag == SECTION_KEYBACKUP)
    {
        cell = [keyBackupSection cellForRowAtRow:rowTag];
    }
#endif
    else if (sectionTag == SECTION_CROSSSIGNING)
    {
        switch (rowTag)
        {
            case CROSSSIGNING_INFO:
            {
                MXKTableViewCellWithTextView *cryptoCell = [self textViewCellForTableView:tableView atIndexPath:indexPath];
                cryptoCell.mxkTextView.attributedText = [self crossSigningInformation];
                cell = cryptoCell;
                break;
            }
            case CROSSSIGNING_FIRST_ACTION:
                cell = [self crossSigningButtonCellInTableView:tableView forAction:CROSSSIGNING_FIRST_ACTION];
                break;
            case CROSSSIGNING_SECOND_ACTION:
                cell = [self crossSigningButtonCellInTableView:tableView forAction:CROSSSIGNING_SECOND_ACTION];
                break;
        }
    }
    else if (sectionTag == SECTION_CRYPTOGRAPHY)
    {
        switch (rowTag)
        {
            case CRYPTOGRAPHY_INFO:
            {
                MXKTableViewCellWithTextView *cryptoCell = [self textViewCellForTableView:tableView atIndexPath:indexPath];
                cryptoCell.mxkTextView.attributedText = [self cryptographyInformation];
                cell = cryptoCell;
                break;
            }
            case CRYPTOGRAPHY_EXPORT:
            {
                MXKTableViewCellWithButton *exportKeysBtnCell = [self buttonCellWithTitle:[VectorL10n securitySettingsExportKeysManually]
                                                                                   action:@selector(exportEncryptionKeys:)
                                                                             forTableView:tableView
                                                                              atIndexPath:indexPath];
                cell = exportKeysBtnCell;
                break;
            }
        }
    }
    else if (sectionTag == SECTION_ADVANCED)
    {
        switch (rowTag)
        {
            case ADVANCED_BLACKLIST_UNVERIFIED_DEVICES:
            {
                MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath];
                
                labelAndSwitchCell.mxkLabel.text = [VectorL10n securitySettingsBlacklistUnverifiedDevices];
                labelAndSwitchCell.mxkSwitch.on = session.crypto.globalBlacklistUnverifiedDevices;
                labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor;
                labelAndSwitchCell.mxkSwitch.enabled = YES;
                [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleBlacklistUnverifiedDevices:) forControlEvents:UIControlEventValueChanged];
                
                cell = labelAndSwitchCell;
                break;
            }
        }
    }
    
    return cell;
}

- (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
    Section *tableSection = [self.tableViewSections sectionAtIndex:section];
    return tableSection.headerTitle;
}

- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section
{
    if ([view isKindOfClass:UITableViewHeaderFooterView.class])
    {
        // Customize label style
        UITableViewHeaderFooterView *tableViewHeaderFooterView = (UITableViewHeaderFooterView*)view;
        tableViewHeaderFooterView.textLabel.textColor = ThemeService.shared.theme.colors.secondaryContent;
        tableViewHeaderFooterView.textLabel.font = ThemeService.shared.theme.fonts.footnote;
    }
}

- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section
{
    NSString *footerTitle = [_tableViewSections sectionAtIndex:section].footerTitle;
    
    if (!footerTitle)
    {
        return nil;
    }
    
    SectionFooterView *view = [tableView dequeueReusableHeaderFooterViewWithIdentifier:SectionFooterView.defaultReuseIdentifier];
    [view updateWithTheme:ThemeService.shared.theme];
    view.leadingInset = tableView.vc_separatorInset.left;
    [view updateWithText:footerTitle];
    
    return view;
}

#pragma mark - UITableView delegate

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath;
{
    cell.backgroundColor = ThemeService.shared.theme.backgroundColor;

    if (cell.selectionStyle != UITableViewCellSelectionStyleNone)
    {
        // Update the selected background view
        if (ThemeService.shared.theme.selectedBackgroundColor)
        {
            cell.selectedBackgroundView = [[UIView alloc] init];
            cell.selectedBackgroundView.backgroundColor = ThemeService.shared.theme.selectedBackgroundColor;
        }
        else
        {
            if (tableView.style == UITableViewStylePlain)
            {
                cell.selectedBackgroundView = nil;
            }
            else
            {
                cell.selectedBackgroundView.backgroundColor = nil;
            }
        }
    }
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (self.tableView == tableView)
    {
        NSIndexPath *tagsIndexPath = [self.tableViewSections tagsIndexPathFromTableViewIndexPath:indexPath];
        NSInteger section = tagsIndexPath.section;
        NSInteger row = tagsIndexPath.row;

        if (section == SECTION_CRYPTO_SESSIONS)
        {
            NSUInteger deviceIndex = row;
            if (deviceIndex < devicesArray.count)
            {
                MXDevice *device = devicesArray[deviceIndex];
                
                if (self.mainSession.crypto.crossSigning.state == MXCrossSigningStateNotBootstrapped)
                {
                    // Display the device details. The verification will fail there.
                    ManageSessionViewController *viewController = [ManageSessionViewController instantiateWithMatrixSession:self.mainSession andDevice:device];
                    
                    [self pushViewController:viewController];
                }
                else if (self.mainSession.crypto.crossSigning.canCrossSign)
                {
                    ManageSessionViewController *viewController = [ManageSessionViewController instantiateWithMatrixSession:self.mainSession andDevice:device];
                    
                    [self pushViewController:viewController];
                }
                else
                {
                    if ([device.deviceId isEqualToString:self.mainSession.matrixRestClient.credentials.deviceId])
                    {
                        [self presentCompleteSecurity];
                    }
                    else
                    {
                        [self presentShouldCompleteSecurityAlert];
                    }
                }
            }
        }

        [tableView deselectRowAtIndexPath:indexPath animated:YES];
    }
}

- (void)presentCompleteSecurity
{
    [[AppDelegate theDelegate] presentCompleteSecurityForSession:self.mainSession];
}

- (void)presentShouldCompleteSecurityAlert
{
    [currentAlert dismissViewControllerAnimated:NO completion:nil];
    
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:[VectorL10n securitySettingsCompleteSecurityAlertTitle]
                                                                             message:[VectorL10n securitySettingsCompleteSecurityAlertMessage]
                                                                      preferredStyle:UIAlertControllerStyleAlert];
    
    [alertController addAction:[UIAlertAction actionWithTitle:[VectorL10n ok]
                                              style:UIAlertActionStyleDefault
                                            handler:^(UIAlertAction * action) {
                                                    [self presentCompleteSecurity];
                                            }]];
    
    [alertController addAction:[UIAlertAction actionWithTitle:[VectorL10n later]
                                              style:UIAlertActionStyleCancel
                                            handler:nil]];
    
    [self presentViewController:alertController animated:YES completion:nil];
    currentAlert = alertController;
    [currentAlert mxk_setAccessibilityIdentifier: @"SettingsVCCompleteSecurity"];
}

#pragma mark - UIDocumentInteractionControllerDelegate

- (void)documentInteractionController:(UIDocumentInteractionController *)controller didEndSendingToApplication:(NSString *)application
{
    // If iOS wants to call this method, this is the right time to remove the file
    [self deleteKeyExportFile];
}

- (void)documentInteractionControllerDidDismissOptionsMenu:(UIDocumentInteractionController *)controller
{
    documentInteractionController = nil;
}

#pragma mark - actions

- (void)exportEncryptionKeys:(UITapGestureRecognizer *)recognizer
{
    [currentAlert dismissViewControllerAnimated:NO completion:nil];

    exportView = [[MXKEncryptionKeysExportView alloc] initWithMatrixSession:self.mainSession];
    currentAlert = exportView.alertController;

    // Use a temporary file for the export
    keyExportsFile = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:@"element-keys.txt"]];

    // Make sure the file is empty
    [self deleteKeyExportFile];

    // Show the export dialog
    MXWeakify(self);
    [exportView showInViewController:self toExportKeysToFile:keyExportsFile onComplete:^(BOOL success) {
        MXStrongifyAndReturnIfNil(self);

        self->currentAlert = nil;
        self->exportView = nil;

        if (success)
        {
            // Let another app handling this file
            self->documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:self->keyExportsFile];
            [self->documentInteractionController setDelegate:self];

            if ([self->documentInteractionController presentOptionsMenuFromRect:self.view.bounds inView:self.view animated:YES])
            {
                // We want to delete the temp keys file after it has been processed by the other app.
                // We use [UIDocumentInteractionControllerDelegate didEndSendingToApplication] for that
                // but it is not reliable for all cases (see http://stackoverflow.com/a/21867096).
                // So, arm a timer to auto delete the file after 10mins.
                self->keyExportsFileDeletionTimer = [NSTimer scheduledTimerWithTimeInterval:600 target:self selector:@selector(deleteKeyExportFile) userInfo:self repeats:NO];
            }
            else
            {
                self->documentInteractionController = nil;
                [self deleteKeyExportFile];
            }
        }
    }];
}

- (void)deleteKeyExportFile
{
    // Cancel the deletion timer if it is still here
    if (keyExportsFileDeletionTimer)
    {
        [keyExportsFileDeletionTimer invalidate];
        keyExportsFileDeletionTimer = nil;
    }

    // And delete the file
    if (keyExportsFile && [[NSFileManager defaultManager] fileExistsAtPath:keyExportsFile.path])
    {
        [[NSFileManager defaultManager] removeItemAtPath:keyExportsFile.path error:nil];
    }
}

- (void)toggleBlacklistUnverifiedDevices:(id)sender
{
    UISwitch *switchButton = (UISwitch*)sender;

    self.mainSession.crypto.globalBlacklistUnverifiedDevices = switchButton.on;

    [self.tableView reloadData];
}

- (void)enablePinCodeSwitchValueChanged:(UISwitch *)sender
{
    SetPinCoordinatorViewMode viewMode = sender.isOn ? SetPinCoordinatorViewModeSetPin : SetPinCoordinatorViewModeConfirmPinToDeactivate;
    self.setPinCoordinatorBridgePresenter = [[SetPinCoordinatorBridgePresenter alloc] initWithSession:self.mainSession viewMode:viewMode];
    self.setPinCoordinatorBridgePresenter.delegate = self;
    [self.setPinCoordinatorBridgePresenter presentFrom:self animated:YES];
}

- (void)enableBiometricsSwitchValueChanged:(UISwitch *)sender
{
    SetPinCoordinatorViewMode viewMode = sender.isOn ? SetPinCoordinatorViewModeSetupBiometricsFromSettings : SetPinCoordinatorViewModeConfirmBiometricsToDeactivate;
    self.setPinCoordinatorBridgePresenter = [[SetPinCoordinatorBridgePresenter alloc] initWithSession:self.mainSession viewMode:viewMode];
    self.setPinCoordinatorBridgePresenter.delegate = self;
    [self.setPinCoordinatorBridgePresenter presentFrom:self animated:YES];
}

- (void)changePinCode:(UIButton *)sender
{
    SetPinCoordinatorViewMode viewMode = SetPinCoordinatorViewModeChangePin;
    self.setPinCoordinatorBridgePresenter = [[SetPinCoordinatorBridgePresenter alloc] initWithSession:self.mainSession viewMode:viewMode];
    self.setPinCoordinatorBridgePresenter.delegate = self;
    [self.setPinCoordinatorBridgePresenter presentFrom:self animated:YES];
}


#pragma mark - SettingsSecureBackupTableViewSectionDelegate

- (void)settingsSecureBackupTableViewSectionDidUpdate:(SettingsSecureBackupTableViewSection *)settingsSecureBackupTableViewSection
{
    [self reloadData];
}

- (MXKTableViewCellWithTextView *)settingsSecureBackupTableViewSection:(SettingsSecureBackupTableViewSection *)settingsSecureBackupTableViewSection textCellForRow:(NSInteger)textCellForRow
{
    MXKTableViewCellWithTextView *cell;
    
    NSIndexPath *indexPath = [self.tableViewSections exactIndexPathForRowTag:textCellForRow sectionTag:SECTION_SECURE_BACKUP];
    
    if (indexPath)
    {
        cell = [self textViewCellForTableView:self.tableView atIndexPath:indexPath];
    }
    
    return cell;
}

- (MXKTableViewCellWithButton *)settingsSecureBackupTableViewSection:(SettingsSecureBackupTableViewSection *)settingsSecureBackupTableViewSection buttonCellForRow:(NSInteger)buttonCellForRow
{
    MXKTableViewCellWithButton *cell;
    
    NSIndexPath *indexPath = [self.tableViewSections exactIndexPathForRowTag:buttonCellForRow sectionTag:SECTION_SECURE_BACKUP];
    
    if (indexPath)
    {
        cell = [self buttonCellForTableView:self.tableView atIndexPath:indexPath];
    }
    
    return cell;
}

- (void)settingsSecureBackupTableViewSectionShowSecureBackupReset:(SettingsSecureBackupTableViewSection *)settingsSecureBackupTableViewSection
{
    [self setupSecureBackup];
}

- (void)settingsSecureBackupTableViewSectionShowKeyBackupCreate:(SettingsSecureBackupTableViewSection *)settingsSecureBackupTableViewSection
{
    [self showKeyBackupSetupFromSignOutFlow:NO];
}

- (void)settingsSecureBackupTableViewSection:(SettingsSecureBackupTableViewSection *)settingsSecureBackupTableViewSection showKeyBackupRecover:(MXKeyBackupVersion *)keyBackupVersion
{
    self.currentkeyBackupVersion = keyBackupVersion;
    
    // If key backup key is stored in SSSS, ask for secrets recovery before restoring key backup.
    if (!self.mainSession.crypto.backup.hasPrivateKeyInCryptoStore
        && self.mainSession.crypto.recoveryService.hasRecovery
        && [self.mainSession.crypto.recoveryService hasSecretWithSecretId:MXSecretId.keyBackup])
    {
        [self showSecretsRecovery];
    }
    else
    {
        [self showKeyBackupRecover:keyBackupVersion fromViewController:self];
    }
}

- (void)settingsSecureBackupTableViewSection:(SettingsSecureBackupTableViewSection *)settingsSecureBackupTableViewSection showKeyBackupDeleteConfirm:(MXKeyBackupVersion *)keyBackupVersion
{
    MXWeakify(self);
    [currentAlert dismissViewControllerAnimated:NO completion:nil];
    
    currentAlert =
    [UIAlertController alertControllerWithTitle:[VectorL10n settingsKeyBackupDeleteConfirmationPromptTitle]
                                        message:[VectorL10n settingsKeyBackupDeleteConfirmationPromptMsg]
                                 preferredStyle:UIAlertControllerStyleAlert];
    
    [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n cancel]
                                                     style:UIAlertActionStyleCancel
                                                   handler:^(UIAlertAction * action) {
        MXStrongifyAndReturnIfNil(self);
        self->currentAlert = nil;
    }]];
    
    [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n settingsKeyBackupButtonDelete]
                                                     style:UIAlertActionStyleDefault
                                                   handler:^(UIAlertAction * action) {
        MXStrongifyAndReturnIfNil(self);
        self->currentAlert = nil;
        
        [self->secureBackupSection deleteKeyBackupWithKeyBackupVersion:keyBackupVersion];
    }]];
    
    [currentAlert mxk_setAccessibilityIdentifier: @"SettingsVCDeleteKeyBackup"];
    [self presentViewController:currentAlert animated:YES completion:nil];
}

- (void)settingsSecureBackupTableViewSection:(SettingsSecureBackupTableViewSection *)settingsSecureBackupTableViewSection showActivityIndicator:(BOOL)show
{
    if (show)
    {
        [self startActivityIndicator];
    }
    else
    {
        [self stopActivityIndicator];
    }
}

- (void)settingsSecureBackupTableViewSection:(SettingsSecureBackupTableViewSection *)settingsSecureBackupTableViewSection showError:(NSError *)error
{
    [[AppDelegate theDelegate] showErrorAsAlert:error];
}

#pragma mark - KeyBackupRecoverCoordinatorBridgePresenter

- (void)showKeyBackupSetupFromSignOutFlow:(BOOL)showFromSignOutFlow
{
    keyBackupSetupCoordinatorBridgePresenter = [[KeyBackupSetupCoordinatorBridgePresenter alloc] initWithSession:self.mainSession];
    
    [keyBackupSetupCoordinatorBridgePresenter presentFrom:self
                                     isStartedFromSignOut:showFromSignOutFlow
                                                 animated:true];
    
    keyBackupSetupCoordinatorBridgePresenter.delegate = self;
}

- (void)keyBackupSetupCoordinatorBridgePresenterDelegateDidCancel:(KeyBackupSetupCoordinatorBridgePresenter *)bridgePresenter {
    [keyBackupSetupCoordinatorBridgePresenter dismissWithAnimated:true];
    keyBackupSetupCoordinatorBridgePresenter = nil;
}

- (void)keyBackupSetupCoordinatorBridgePresenterDelegateDidSetupRecoveryKey:(KeyBackupSetupCoordinatorBridgePresenter *)bridgePresenter {
    [keyBackupSetupCoordinatorBridgePresenter dismissWithAnimated:true];
    keyBackupSetupCoordinatorBridgePresenter = nil;
    
    [secureBackupSection reload];
}

#pragma mark - SettingsKeyBackupTableViewSectionDelegate
#ifdef CROSS_SIGNING_AND_BACKUP_DEV
- (void)settingsKeyBackupTableViewSectionDidUpdate:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection
{
    [self reloadData];
}

- (MXKTableViewCellWithTextView *)settingsKeyBackupTableViewSection:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection textCellForRow:(NSInteger)textCellForRow
{
    MXKTableViewCellWithTextView *cell;
    
    NSIndexPath *indexPath = [self.tableViewSections exactIndexPathForRowTag:textCellForRow sectionTag:SECTION_KEYBACKUP];
    
    if (indexPath)
    {
        cell = [self textViewCellForTableView:self.tableView atIndexPath:indexPath];
    }
    
    return cell;
}

- (MXKTableViewCellWithButton *)settingsKeyBackupTableViewSection:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection buttonCellForRow:(NSInteger)buttonCellForRow
{
    MXKTableViewCellWithButton *cell;
    
    NSIndexPath *indexPath = [self.tableViewSections exactIndexPathForRowTag:buttonCellForRow sectionTag:SECTION_KEYBACKUP];
    
    if (indexPath)
    {
        cell = [self buttonCellForTableView:self.tableView atIndexPath:indexPath];
    }
    
    return cell;
}

- (void)settingsKeyBackupTableViewSectionShowKeyBackupSetup:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection
{
    [self showKeyBackupSetupFromSignOutFlow:NO];
}

- (void)settingsKeyBackup:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection showKeyBackupRecover:(MXKeyBackupVersion *)keyBackupVersion
{
    self.currentkeyBackupVersion = keyBackupVersion;
    
    // If key backup key is stored in SSSS ask for secrets recovery before restoring key backup.
    if (!self.mainSession.crypto.backup.hasPrivateKeyInCryptoStore
        && self.mainSession.crypto.recoveryService.hasRecovery
        && [self.mainSession.crypto.recoveryService hasSecretWithSecretId:MXSecretId.keyBackup])
    {
        [self showSecretsRecovery];
    }
    else
    {
        [self showKeyBackupRecover:keyBackupVersion fromViewController:self];
    }
}

- (void)settingsKeyBackup:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection showKeyBackupDeleteConfirm:(MXKeyBackupVersion *)keyBackupVersion
{
    MXWeakify(self);
    [currentAlert dismissViewControllerAnimated:NO completion:nil];

    currentAlert =
    [UIAlertController alertControllerWithTitle:[VectorL10n  settingsKeyBackupDeleteConfirmationPromptTitle]
                                        message:[VectorL10n  settingsKeyBackupDeleteConfirmationPromptMsg]
                                 preferredStyle:UIAlertControllerStyleAlert];

    [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n cancel]
                                                     style:UIAlertActionStyleCancel
                                                   handler:^(UIAlertAction * action) {
                                                       MXStrongifyAndReturnIfNil(self);
                                                       self->currentAlert = nil;
                                                   }]];

    [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n  settingsKeyBackupButtonDelete]
                                                     style:UIAlertActionStyleDefault
                                                   handler:^(UIAlertAction * action) {
                                                       MXStrongifyAndReturnIfNil(self);
                                                       self->currentAlert = nil;

                                                       [self->keyBackupSection deleteWithKeyBackupVersion:keyBackupVersion];
                                                   }]];

    [currentAlert mxk_setAccessibilityIdentifier: @"SettingsVCDeleteKeyBackup"];
    [self presentViewController:currentAlert animated:YES completion:nil];
}

- (void)settingsKeyBackup:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection showActivityIndicator:(BOOL)show
{
    if (show)
    {
        [self startActivityIndicator];
    }
    else
    {
        [self stopActivityIndicator];
    }
}

- (void)settingsKeyBackup:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection showError:(NSError *)error
{
    [[AppDelegate theDelegate] showErrorAsAlert:error];
}

#endif

#pragma mark - KeyBackupRecoverCoordinatorBridgePresenter

- (void)showKeyBackupRecover:(MXKeyBackupVersion*)keyBackupVersion fromViewController:(UIViewController*)presentingViewController
{
    keyBackupRecoverCoordinatorBridgePresenter = [[KeyBackupRecoverCoordinatorBridgePresenter alloc] initWithSession:self.mainSession keyBackupVersion:keyBackupVersion];

    [keyBackupRecoverCoordinatorBridgePresenter presentFrom:presentingViewController animated:true];
    keyBackupRecoverCoordinatorBridgePresenter.delegate = self;
}
    
- (void)pushKeyBackupRecover:(MXKeyBackupVersion*)keyBackupVersion fromNavigationController:(UINavigationController*)navigationController
{
    keyBackupRecoverCoordinatorBridgePresenter = [[KeyBackupRecoverCoordinatorBridgePresenter alloc] initWithSession:self.mainSession keyBackupVersion:keyBackupVersion];
    
    [keyBackupRecoverCoordinatorBridgePresenter pushFrom:navigationController animated:YES];
    keyBackupRecoverCoordinatorBridgePresenter.delegate = self;
}

- (void)keyBackupRecoverCoordinatorBridgePresenterDidCancel:(KeyBackupRecoverCoordinatorBridgePresenter *)bridgePresenter {
    [keyBackupRecoverCoordinatorBridgePresenter dismissWithAnimated:true];
    keyBackupRecoverCoordinatorBridgePresenter = nil;
    secretsRecoveryCoordinatorBridgePresenter = nil;
}

- (void)keyBackupRecoverCoordinatorBridgePresenterDidRecover:(KeyBackupRecoverCoordinatorBridgePresenter *)bridgePresenter {
    [keyBackupRecoverCoordinatorBridgePresenter dismissWithAnimated:true];
    keyBackupRecoverCoordinatorBridgePresenter = nil;
    secretsRecoveryCoordinatorBridgePresenter = nil;
}
    
#pragma mark - KeyBackupRecoverCoordinatorBridgePresenter
    
- (void)showSecretsRecovery
{
    secretsRecoveryCoordinatorBridgePresenter = [[SecretsRecoveryCoordinatorBridgePresenter alloc] initWithSession:self.mainSession recoveryGoal:SecretsRecoveryGoalBridgeKeyBackup];
    
    [secretsRecoveryCoordinatorBridgePresenter presentFrom:self animated:true];
    secretsRecoveryCoordinatorBridgePresenter.delegate = self;
}

- (void)secretsRecoveryCoordinatorBridgePresenterDelegateDidCancel:(SecretsRecoveryCoordinatorBridgePresenter *)coordinatorBridgePresenter
{
    [secretsRecoveryCoordinatorBridgePresenter dismissWithAnimated:YES completion:nil];
    secretsRecoveryCoordinatorBridgePresenter = nil;
}

- (void)secretsRecoveryCoordinatorBridgePresenterDelegateDidComplete:(SecretsRecoveryCoordinatorBridgePresenter *)coordinatorBridgePresenter
{
    UIViewController *presentedViewController = [coordinatorBridgePresenter toPresentable];
    
    if (coordinatorBridgePresenter.recoveryGoal == SecretsRecoveryGoalBridgeKeyBackup)
    {
        // Go to the true key backup recovery screen
        if ([presentedViewController isKindOfClass:UINavigationController.class])
        {
            UINavigationController *navigationController = (UINavigationController*)self.presentedViewController;
            [self pushKeyBackupRecover:self.currentkeyBackupVersion fromNavigationController:navigationController];
        }
        else
        {
            [self showKeyBackupRecover:self.currentkeyBackupVersion fromViewController:presentedViewController];
        }
    }
    else
    {
        [secretsRecoveryCoordinatorBridgePresenter dismissWithAnimated:YES completion:nil];
        secretsRecoveryCoordinatorBridgePresenter = nil;
    }
}

#pragma mark - SecureBackupSetupCoordinatorBridgePresenterDelegate

- (void)secureBackupSetupCoordinatorBridgePresenterDelegateDidComplete:(SecureBackupSetupCoordinatorBridgePresenter *)coordinatorBridgePresenter
{
    [self.secureBackupSetupCoordinatorBridgePresenter dismissWithAnimated:YES completion:nil];
    self.secureBackupSetupCoordinatorBridgePresenter = nil;
}

- (void)secureBackupSetupCoordinatorBridgePresenterDelegateDidCancel:(SecureBackupSetupCoordinatorBridgePresenter *)coordinatorBridgePresenter
{
    [self.secureBackupSetupCoordinatorBridgePresenter dismissWithAnimated:YES completion:nil];
    self.secureBackupSetupCoordinatorBridgePresenter = nil;
}

#pragma mark - SetPinCoordinatorBridgePresenterDelegate

- (void)setPinCoordinatorBridgePresenterDelegateDidComplete:(SetPinCoordinatorBridgePresenter *)coordinatorBridgePresenter
{
    [self.tableView reloadData];
    [self dismissViewControllerAnimated:YES completion:nil];
}

- (void)setPinCoordinatorBridgePresenterDelegateDidCancel:(SetPinCoordinatorBridgePresenter *)coordinatorBridgePresenter
{
    [self.tableView reloadData];
    [self dismissViewControllerAnimated:YES completion:nil];
}

#pragma mark - TableViewSectionsDelegate

- (void)tableViewSectionsDidUpdateSections:(TableViewSections *)sections
{
    [self.tableView reloadData];
}

@end
